<template>
  <div class="graphContainer" ref="mountNode">
    <i class="el-icon-full-screen fangda" @click="fangda"></i>
    <div :id="treeId" style="height: 100%; width: 100%; background-color: #fff" ref="parentContent"></div>
    <el-dialog title="指标信息" :visible.sync="formDialog" width="40%">
      <el-form size="mini">
        <el-form-item label="赋值">
          <el-input v-model="nodeForm.num" oninput ="value=value.replace(/[^0-9.]/g,'')" :disabled="numInput"></el-input>
        </el-form-item>
        <el-form-item label="指标名称">
          <el-input v-model="nodeForm.label" :disabled="modelDis"></el-input>
        </el-form-item>
        <el-form-item label="宽">
          <el-col :span="11">
            <el-input
              :disabled="modelDis"
              placeholder="请输入不小于120的数字"
              v-model="nodeForm.width"
              @input="validwidth"
              @blur="widthBlur"
            ></el-input>
          </el-col>
          <el-col class="line" :span="2"></el-col>
          <el-col :span="11"></el-col>
        </el-form-item>
        <el-form-item label="高">
          <el-col :span="11">
            <el-input
              :disabled="modelDis"
              placeholder="请输入不小于50的数字"
              v-model="nodeForm.height"
              @input="validheight"
              @blur="heightBlur"
            ></el-input>
          </el-col>
          <el-col class="line" :span="2"></el-col>
          <el-col :span="11"></el-col>
        </el-form-item>
      </el-form>
      <span slot="footer" class="dialog-footer">
        <el-button size="mini" @click="formDialog = false">取 消</el-button>
        <el-button size="mini" type="primary" @click="commit"> 确 定 </el-button>
      </span>
    </el-dialog>
  </div>
</template>

<script>
  import G6 from "@antv/g6";
  import screenfull from "screenfull";
  import insertCss from "insert-css";

  export default {
    name: "G6Container",
    props: {
      // 节点dom id 防止一个页面引入两次组件而出现同样的dom id
      treeId: {
        type: String,
        default: "mountNode",
      },
      treedata: {
        required: true,
      },
      numInput:{
        required: false,//编辑弹框数值是否能输入
      },
      modelDis:{
        required: false,//编辑弹框数值是否能输入

      },
      // 是否右键显示操作按钮
      operate: {
        type: Boolean,
        default: false,
      },
    },
    data() {
      return {
        myTree: null,
        graph: null,
        currIsFull: false,
        actType: "", // 操作类型，有添加下级 删除节点 和 编辑 三种操作
        formDialog: false,
        currNode: null, // 当前节点 添加和编辑的时候会用到
        nodeForm: {
          num: "",
          label: "",
          width: 120,
          height: 50,
        },
        width: 0,
        height: 0,
      };
    },
    watch: {
      // 监听 obj 的变化，包括其嵌套属性的变化
      treedata: {
        handler(newVal, oldVal) {
          let _this = this
          this.myTree = JSON.parse(JSON.stringify(newVal));
          console.log(this.myTree,'G6最终数据')
          _this.graph.changeData(this.myTree);
          // _this.graph.downloadFullImage('tree-graph', 'image/png', {
          //   backgroundColor: '#ddd',
          //   padding: [30, 15, 15, 15],
          // });

          if (_this.actType !== "EDITITEM") {
            _this.graph.refresh();
          }
        },
        deep: true, // 深度监听
        immediate: false, // 立即执行一次监听函数
      },
    },
    created() {
      // 这是 鼠标滑入时 弹窗的样式，官网示例是这样的  需要安装引入 inserCss
      insertCss(`
      .g6-tooltip {
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
        background-color: rgba(255, 255, 255, 0.9);
        padding: 10px 8px;
        box-shadow: rgb(174, 174, 174) 0px 0px 10px;
      }
    `);
      // 这是右键点击节点出现的弹窗的样式
      insertCss(`
      .g6-component-contextmenu {
        position: absolute;
        list-style-type: none;
        padding: 10px 8px;
        left: -150px;
        background-color: rgba(255, 255, 255, 0.9);
        border: 1px solid #e2e2e2;
        border-radius: 4px;
        font-size: 12px;
        color: #545454;
      }
      .g6-component-contextmenu ul {
        padding: 0;
        margin: 0;
      }
      .g6-component-contextmenu li {
        cursor: pointer;
        list-style-type:none;
        list-style: none;
        margin-left: 0px;
      }
      .g6-component-contextmenu li:hover {
        color: #aaa;
      }
    `);
    },
    mounted() {

      const container = document.getElementById(this.treeId);
      this.width = container.scrollWidth;
      this.height = container.scrollHeight;
      // this.myTree = this.treedata;
      // if(Object.keys(this.treedata).length !== 0){
        this.myTree = JSON.parse(JSON.stringify(this.treedata));
        this.initGraph(this.myTree);
        // 添加 浏览器窗口改变大小时的监听
        this.initSize()
      // }

    },
    methods: {
      refer(){
        if (this.graph) {
          this.graph.clear();
        }
      },
      queryBase64(){
        if(this.myTree){
          const dataURL = this.graph.toDataURL().split(',');
          this.$emit('imgBase64', dataURL);
        }
      },
      initSize () {
        const self = this // 因为箭头函数会改变this指向，指向windows。所以先把this保存
        setTimeout(() => {
          // todo 浏览器窗口发生变化时
          window.onresize = function () {
            // todo 获取div parentContent 的宽度和高度
            this.canvasWidth = self.$refs.mountNode.clientWidth
            this.canvasHeight = self.$refs.mountNode.clientHeight
            // todo 修改画布的大小
            self.graph.changeSize(this.canvasWidth, this.canvasHeight)
            // todo 将图移动到画布中心位置
            self.graph.fitCenter()
          }
        }, 20)
      },
      validwidth(value) {
        // 使用Number函数将输入转换为数字，如果转换失败则value为NaN
        const parsedValue = Number(value);
        if (parsedValue.length > 1) {
          // 检查转换是否成功，并且值不小于20
          if (isNaN(parsedValue) || parsedValue < 20) {
            // 如果不满足条件，将inputValue重置为20
            this.$set(this.nodeForm, "width", 120);
            // 可以选择性地显示一个错误消息
            this.$message.error("请输入不小于20的有效数字！");
          } else {
            // 如果满足条件，更新inputValue的值
            this.$set(this.nodeForm, "width", parsedValue);
          }
        }
      },
      widthBlur() {
        if (this.nodeForm.width < 120) {
          this.$set(this.nodeForm, "width", 120);
          // 可以选择性地显示一个错误消息
          this.$message.error("请输入不小于120的有效数字！");
        }
      },
      validheight(value) {
        // 使用Number函数将输入转换为数字，如果转换失败则value为NaN
        const parsedValue = Number(value);
        if (parsedValue.length > 1) {
          // 检查转换是否成功，并且值不小于50
          if (isNaN(parsedValue) || parsedValue < 50) {
            // 如果不满足条件，将inputValue重置为50
            this.$set(this.nodeForm, "height", 50);
            // 可以选择性地显示一个错误消息
            this.$message.error("请输入不小于50的有效数字！");
          } else {
            // 如果满足条件，更新inputValue的值
            this.$set(this.nodeForm, "height", parsedValue);
          }
        }
      },
      heightBlur() {
        if (this.nodeForm.height < 50) {
          this.$set(this.nodeForm, "height", 50);
          // 可以选择性地显示一个错误消息
          this.$message.error("请输入不小于50的有效数字！");
        }
      },
       uuid() {
        var s = [];
        var hexDigits = "0123456789abcdef";
        for (var i = 0; i < 32; i++) {
          s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
        }
        s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
        s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
        s[8] = s[13] = s[18] = s[23];
        var uuid = s.join("");
        return uuid;

  },
      commit() {
        if (this.actType === "ADDCHILD") {
          if(this.nodeForm.label =='') return this.$message.warning('指标名称不能为空！')
          // let id = this.generateUniqueId(this.myTree);
          this.$set(this.nodeForm, "id", this.uuid());
          this.$set(this.nodeForm, "pid", this.currNode.id);
          this.$set(this.nodeForm, "size", [
            Number(this.nodeForm.width),
            Number(this.nodeForm.height),
          ]);
          this.$delete(this.nodeForm, "width");
          this.$delete(this.nodeForm, "height");
          this.addChildNode(this.myTree, this.currNode.id, this.nodeForm);
          // 解构赋值
          let myTree = {...this.myTree};
          setTimeout(() => {
            this.graph.changeData(myTree);
          }, 200);
          // this.graph.changeData(this.myTree);
          // this.graph.refresh();
          // this.$emit("addTree", myTree);
          this.$emit("addTree", this.nodeForm);
        }
        else if (this.actType === "EDITITEM") {
          // 树节点权重赋值校验
          let total = Number(this.nodeForm.num)
          this.myTree.children.forEach(item => {
            if (item.id != this.currNode.id) {
              total = total + Number(item.num)
            }
          })
          if (total > 1) {
            return this.$message.warning('子结点权重最大为1！')
          }
          // 编辑后赋值
          this.$set(this.nodeForm, "size", [
            Number(this.nodeForm.width),
            Number(this.nodeForm.height),
          ]);
            this.updateNode(this.myTree, this.currNode.id, this.nodeForm);
            this.getNewTree(this.myTree)//节点的width和height必须为number
            if(this.myTree.width || this.myTree.height){this.myTree.width=Number(this.myTree.width);this.myTree.height=Number(this.myTree.height)}
            let myTree = {...this.myTree};
            this.$emit("updateTree", myTree);
            setTimeout(() => {
              this.graph.changeData(myTree);
            }, 200);

        }

        this.formDialog = false;
      },
      resize() {
        this.currIsFull = screenfull.isFullscreen;
        if (this.currIsFull) {
          this.graph.changeSize(window.innerWidth, window.innerHeight);
        } else {
          this.graph.changeSize(this.width, this.height);
        }

      },
      resizeListener() {
        window.addEventListener("resize", this.resize);
      },
      fangda() {
        this.currIsFull = !this.currIsFull;
        screenfull.toggle(this.$refs[this.treeId]);
        this.resize();
      },
      initMenu() {
        var _this = this
        this.contextMenu = new G6.Menu({

          getContent(evt) {
            if(!_this.modelDis){
              return `
              <ul>
                <li id="child">添加下级</li>
                <li id="delete">删除节点</li>
                <li id="edit">编辑</li>
              </ul>`;
            }else{
              return `
              <ul>
                <li id="edit">编辑</li>
              </ul>`;
            }
          },
          handleMenuClick: (target, item) => {
            let name = target.id;
            let model = item._cfg.model;
            if (name === "child") {
              this.currNode = model;
              this.addChild();
            } else if (name === "edit") {
              this.currNode = model;
              this.editItem();
            } else if (name === "delete") {
              this.currNode = model;
              this.delItem();
            }
          },
          // 显示菜单时 的坐标偏移量
          offsetX: -5,
          // 显示菜单时 的坐标偏移量
          offsetY: -5,
          // 在哪些类型的元素上响应
          itemTypes: ["node"],
        });
      },
      initGraph(data) {
        // 注册节点 自定义样式
        this.registerNode();
        // 注册边 自定义边的路线
        this.registerEdge();
        if (this.operate) {
          this.initMenu();
        }
        // 创建 树形图  关于树形，具体描述可以去官网查
        this.graph = new G6.TreeGraph({
          container: this.treeId,
          // 线是否连在中间，
          linkCenter: true,
          // 右键菜单
          plugins: [this.operate ? this.contextMenu : ""],
          // 绘制动画
          animate: true,
          // 适应界面
          fitView: true,
          // maxZoom: 1, // 最大比例
          // minZoom: 0.5, // 最小比例
          // fitViewPadding: [0, 0, 0, 0], // 边距  fitView: true时生效  根据需要使用
          width: this.width,
          autoPaint: true,
          height: this.height,
          // 画在中间
          fitCenter: true,
          // 拖拽节点 拖拽画布 缩放画布 鼠标滑入的弹窗内容
          modes: {
            default: [
              "drag-node",
              "drag-canvas",
              "zoom-canvas",
              {
                type: "tooltip",
                formatText: function formatText(model) {
                  const text = `数值: ${model.num}<br/>名称: ${model.label}`;
                  return text;
                },

                shouldUpdate: function shouldUpdate() {
                  return true;
                },
              },
            ],
          },
          renderer: 'canvas', // 渲染方式
          enabledStack: false ,// 是否开启 redo & undo 功能
          // 默认节点类型
          defaultNode: {
            shape: "card-node",
            type: "card-node",
          },
          // 默认边类型
          defaultEdge: {
            shape: "card-edge",
            type: "card-edge",
            style: {
              stroke: "#91d5ff",
            },
          },
          // 布局的类型
          layout: {
            type: "compactBox",
            direction: "TB",
            getId: function getId(d) {
              return d.id;
            },
            // 每个节点的垂直间隙
            getVGap: function getVGap(e) {
              return e.size[1];
            },
            // 每个节点的水平间隙
            getHGap: function getHGap(e) {
              return e.size[0];
            },
          },
          scroller: {
            enabled: true,
            pannable: true,
            pageVisible: true,
            pageBreak: false,
          },
          mousewheel: {
            enabled: true,
            global: true,
            modifiers: ["ctrl", "meta"],
          },
        });
        // this.graph.positionsAnimate();
        this.graph.data(data);
        this.graph.render();
        // 适应 dom 容器
        this.graph.fitView();
      },
      addChild() {
        this.actType = "ADDCHILD";
        this.nodeForm = {
          num: "",
          label: "",
          width: 120,
          height: 50,
        };
        this.formDialog = true;
      },
      editItem() {
        this.actType = "EDITITEM";
        this.nodeForm = JSON.parse(JSON.stringify(this.currNode));
        this.$set(this.nodeForm, "width", this.currNode.size[0]);
        this.$set(this.nodeForm, "height", this.currNode.size[1]);
        this.$delete(this.nodeForm, "children");
        this.formDialog = true;
      },
      delItem() {
        this.$confirm("确认删除此节点及其子节点？")
          .then((_) => {
            if(this.treedata.id == this.currNode.id){
              // console.log(this.currNode, this.treedata, '删除根结点');
              this.graph.clear();
            }else{
              this.graph.removeItem(this.currNode.id);
              this.removeNode(this.myTree, this.currNode.id);
              let myTree = {...this.myTree};
              // this.$emit("updateTree", myTree);
              // this.$emit("deleteTree", myTree );
            }
            this.$emit("deleteTree", this.currNode );
          })
          .catch((_) => {
          });
      },
      resetGraphData(data) {
        this.graph.clear();
        this.graph.data(data);
        this.graph.render();
        // 适应 dom 容器
        // this.graph.fitView();
      },
      registerNode() {
        let that = this;
        // 文字超出的话末尾换成 ...  官网示例中有
        const fittingString = (str, maxWidth, fontSize) => {
          const ellipsis = "...";
          const ellipsisLength = G6.Util.getTextSize(ellipsis, fontSize)[0];
          let currentWidth = 0;
          let res = str;
          const pattern = new RegExp("[\u4E00-\u9FA5]+"); // 区分汉字和字母
          str.split("").forEach((letter, i) => {
            if (currentWidth > maxWidth - ellipsisLength) return;
            if (pattern.test(letter)) {
              currentWidth += fontSize;
            } else {
              // 根据fontSize获取单个字母的宽度
              currentWidth += G6.Util.getLetterWidth(letter, fontSize);
            }
            if (currentWidth > maxWidth - ellipsisLength) {
              res = `${str.substr(0, i)}${ellipsis}`;
            }
          });
          // console.log(res,'res')
          return res;
        };

        // 文字超出宽度则换行，若高度也不够 则在末尾显示 ...
        function formatText(text, maxWidth, maxHeight, fontSize) {
          const words = text.split("");
          let formattedText = ""; // 已经拼接好的文字
          let currentHeight = G6.Util.getTextSize(words[0], fontSize)[1]; // 当前高度
          let currentLine = ""; // 当前行文字
          let currentWidth = 0; // 当前行宽度
          for (let i = 0; i < words.length; i++) {
            const word = words[i];
            const wordWidth = G6.Util.getLetterWidth(word, fontSize);
            currentLine += word;
            currentWidth += wordWidth;
            if (currentWidth + wordWidth > maxWidth) {
              currentLine += "\n";
              formattedText += currentLine;
              currentLine = ""; // 重置当前行
              currentWidth = 0; // 重置当前宽度
              currentHeight += G6.Util.getTextSize(word, fontSize)[1]; // 增加当前高度
              // console.log(text, "currentHeight", currentHeight, maxHeight);
              // 如果当前高度超过最大高度，则结束循环
              if (currentHeight > maxHeight) {
                if (
                  G6.Util.getTextSize(
                    currentLine.substr(currentLine.length - 1, 1),
                    fontSize
                  )[0] > G6.Util.getTextSize("...", fontSize)[0]
                ) {
                  formattedText = formattedText.slice(0, -1) + "...";
                } else {
                  formattedText = formattedText.slice(0, -2) + "...";
                }
                break;
              }
            } else {
              if (i === words.length - 1) {
                formattedText += currentLine;
              }
            }
            // 添加单词到当前行，保留单词前的空格
          }

          return formattedText;
        }

        // 文本超出隐藏 (字段, 最大长度, 字体大小) https://blog.csdn.net/kaimo313/article/details/126889346
        // https://blog.csdn.net/qq_45796592/article/details/132637275
        G6.registerNode("card-node", {
          draw: function drawShape(cfg, group) {
            const r = 4;
            const w = cfg.size ? cfg.size[0] : 120;
            const h = cfg.size[1];
            const data = cfg;
            // 画一个 长方体 容器
            const shape = group.addShape("rect", {
              attrs: {
                x: -w / 2,
                y: -h / 2,
                width: w,
                height: h,
                // 圆角
                radius: r,
                // 填充颜色
                fill: "#fff",
                shadowOffsetX: 0,
                shadowOffsetY: 4,
                shadowColor: "rgba(0,0,0,0.1)",
                shadowBlur: 8,
              },
              name: "main-box",
              draggable: true,
            });
            // 画一个长方形容器，用来放 数值
            group.addShape("rect", {
              attrs: {
                x: -w / 2,
                y: -h / 2,
                width: w,
                height: 24,
                fill: "#3670F5",
                radius: [r, r, 0, 0],
              },
              name: "top-box",
              draggable: true,
            });
            // 把数值放在 容器内
            const cardNumberText = group.addShape("text", {
              attrs: {
                fontSize: 12,
                lineHeight: 22,
                // 超出显示...
                text: fittingString(data.num, w + 2, 12),
                fill: "#fff",
              },
              name: "institution-title",
              draggable: true,
            });
            const cardNumberTextBox = cardNumberText.getBBox();
            cardNumberText.attr({
              x: -w / 2 + 4,
              y: -h / 2 + cardNumberTextBox.height + 8,
            });
            // 存放label的文字
            const institutionText = group.addShape("text", {
              attrs: {
                fontSize: 12,
                lineHeight: 12,
                // 换行 和 超出显示...
                text: formatText(data.label, w - 8, h - 8 - 30, 12),
                fill: "#000",
              },
              name: "institution-title",
            });
            const institutionTextBox = institutionText.getBBox();
            institutionText.attr({
              x: -w / 2 + 4,
              y: -h / 2 + institutionTextBox.height + 30,
            });
            // 如果有子节点 则显示 收起 节点的小圆圈
            cfg.children &&
            cfg.children.length &&
            group.addShape("marker", {
              attrs: {
                x: 0,
                y: h / 2,
                r: 6,
                cursor: "pointer",
                symbol: G6.Marker.collapse,
                stroke: "#666",
                lineWidth: 1,
                fill: "#fff",
              },
              name: "collapse-icon",
            });
            return shape;
          },
          setState(name, value, item) {
            if (name === "collapsed") {
              const marker = item
                .get("group")
                .find((ele) => ele.get("name") === "collapse-icon");
              const icon = value ? G6.Marker.expand : G6.Marker.collapse;
              marker.attr("symbol", icon);
            }
          },
        });
      },
      registerEdge() {
        // https://blog.csdn.net/study_way/article/details/134142149
        G6.registerEdge("card-edge", {
          draw(cfg, group) {
            const startPoint = cfg.startPoint;
            const endPoint = cfg.endPoint;

            const {style} = cfg;
            const shape = group.addShape("path", {
              attrs: {
                stroke: style.stroke,
                path: [
                  ["M", startPoint.x, startPoint.y],
                  ["L", startPoint.x, (startPoint.y + endPoint.y) / 2],
                  ["L", endPoint.x, (startPoint.y + endPoint.y) / 2],
                  ["L", endPoint.x, endPoint.y],
                ],
              },
            });

            return shape;
          },
        });
      },
      addChildNode(data, parentId, newNode) {
        // 查找父节点
        function findParent(node, id) {
          if (node.id == id) return node;
          if (node.children) {
            for (let child of node.children) {
              const result = findParent(child, id);
              if (result) return result;
            }
          }
          return null;
        }

        const parent = findParent(data, parentId);
        if (parent.depth >= 9) {
          this.$message.error("不能大于10级");
          return;
        }
        if (parent) {
          // 在父节点的children数组中添加新节点
          if (!(parent.children instanceof Array)) {
            parent.children = [];
          }
          parent.children.push(newNode);

        }
      },
      getNewTree(obj){
        if(obj.children){
          obj.children.map(item=>{
            if(item.width){
              item.width=Number(item.width)
            }
            item.width=Number(item.width)
            if(item.children&&item.children.length>0){
              this.getNewTree(item.children)
            }
          })
        }

        return obj
      },
      updateNode(data, nodeId, updates) {
        // 深度优先搜索来查找并更新节点
        function dfs(node) {
          if (node.id === nodeId) {
            Object.keys(updates).forEach((key) => {
              node[key] = updates[key];
            });
            return true;
          }
          if (node.children) {
            for (let child of node.children) {
              if (dfs(child)) return true;
            }
          }
          return false;
        }
        dfs(data);
      },
      removeNode(data, nodeId) {
        // 深度优先搜索来查找并删除节点
        function dfs(node, parentId) {
          if (!node.children) return null;
          const index = node.children.findIndex((child) => child.id === nodeId);
          if (index !== -1) {
            // 删除找到的节点
            node.children.splice(index, 1);
            return true;
          }
          for (let child of node.children) {
            if (dfs(child, node.id)) return true;
          }
          return false;
        }

        dfs(data, null);
      },
      // 生成一个树中没有的id
      generateUniqueId(data, existingIds = new Set()) {
        // 遍历当前节点的所有子节点，并收集它们的ID
        function collectIds(node) {
          if (node.id) {
            existingIds.add(node.id);
          }
          if (node.children) {
            for (const child of node.children) {
              collectIds(child);
            }
          }
        }

        // 从根节点开始收集ID
        collectIds(data);

        // 生成一个新的ID并确保它是唯一的
        let newId = "1"; // 你可以根据需要调整初始ID的值或格式
        while (existingIds.has(newId)) {
          // 这里简单地通过递增数字来生成新的ID，但你可以根据需要使用更复杂的策略
          newId = (parseInt(newId) + 1).toString();
        }

        return newId;
      },
    },
  };
</script>

<style scoped>
  .graphContainer {
    position: relative;
    width: 100%;
    height: 100%;

  .fangda {
    position: absolute;
    cursor: pointer;
    right: 10px;
    top: 10px;
    height: 30px;
    width: 30px;
  }

  }
</style>
